package br.unb.cic.algostep.leitor;

import java.io.BufferedReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import br.unb.cic.algostep.gui.arquivos.GerenciadorArquivos;
import br.unb.cic.algostep.gui.console.Console;
import br.unb.cic.algostep.gui.editortexto.EditorTexto;
import br.unb.cic.algostep.gui.variaveis.Mostrador;
import br.unb.cic.algostep.gui.variaveis.Variavel;

/**
 * Faz a leitura da tabela de instruções e as repassa para o módulo
 * responsável.
 * 
 * @author André Gasparinetti, Renan Leandro Ferreira
 */
public class Leitor {
	
	private ArrayList<Estado> estado;
	private Map<String,ArrayList<Variavel>> records;
	
    Mostrador mostrador;
    EditorTexto editor;
    Console console;
    GerenciadorArquivos ga;
    
    /**
     * Gera a lista de estados, a partir de um Reader. A lista gerada
     * é obtida pelo método getListaEstado()
     * @param mostrador
     * @param editor
     * @param leitor
     * @throws Exception
     */
    public Leitor(Mostrador mostrador, EditorTexto editor, Reader leitor, Console console, GerenciadorArquivos ga) throws Exception { 
    	this.mostrador = mostrador;
    	this.editor = editor;
    	this.console = console;
    	this.ga = ga;

		BufferedReader buffer = new BufferedReader(leitor);
		String linha = null;
		estado = new ArrayList<Estado>();
		records = new HashMap<String,ArrayList<Variavel>>();

		while (true) {
			linha = buffer.readLine();
			
			if (linha != null)			
				analisarLexico(linha);
			else
				break;	
		}
    }
    
    /**
     * Retorna a lista de estados criada pelo construtor da classe
	 */
	public ArrayList<Estado> getListaEstado() {
		return estado;
	}
	
	/**
	 * Identifica os tokens de uma linha.Se todo o processo correr
	 * corretamente retorna "true". Caso algum erro seja detectado,
	 * retorna "false".
	*/
	private void analisarLexico(String linha) throws Exception{
		int i = 0;
		char[] buffer = linha.toCharArray();
		ArrayList<TokenTabela> lista = new ArrayList<TokenTabela>();
		
		while(i < buffer.length){
			int estado = E_COMENTARIO_0;
			String TempToken = "";
			boolean achou = false;
			
			while((!achou) && (i < buffer.length)){
				char a = buffer[i];
				switch(estado){
					case E_COMENTARIO_0:
						if(a == '#'){
							i = buffer.length;
						}
						else{
							i--;
							estado = falhar(estado);
						}
						break;
					case E_ESPACO_BRANCO_0:
						if ((a == ' ')  || (a == '{') || (a == '}')){
							achou = true;
						}
						else{
							i--;
							estado = falhar(estado);
						}
						break;
					case E_INTEIRO_0:
						if(Character.isDigit(a)){
							TempToken += a;
							estado = E_INTEIRO_1;
						}
						else{
							i--;
							estado = falhar(estado);
						}
						break;
					case E_INTEIRO_1:
						if(Character.isDigit(a)){
							TempToken += a;
						}
						else if ((a == ' ') || (a == ';') || (a == ',')){
							lista.add(new TokenTabela(TokenTabela.IDTKN_INTEIRO,
									TempToken));
							i--;
							achou = true;
						}
						else if (a == '.'){
							TempToken += a;
							estado = E_REAL_2;
						}
						break;
					case E_TRUE_0:
						if (a == 't'){
							TempToken += a;
							estado = E_TRUE_1;
						}
						else{
							i--;
							estado = falhar(estado);
						}
						break;
					case E_TRUE_1:
						if (a == 'r'){
							TempToken += a;
							estado = E_TRUE_2;
						}
						else{
							i -= TempToken.length();
							estado = falhar(estado);
						}
						break;
					case E_TRUE_2:
						if (a == 'u'){
							TempToken += a;
							estado = E_TRUE_3;
						}
						else{
							i -= TempToken.length();
							estado = falhar(estado);
						}
						break;
					case E_TRUE_3:
						if (a == 'e'){
							TempToken += a;
							estado = E_TRUE_4;
						}
						else{
							i -= TempToken.length();
							estado = falhar(estado);
						}
						break;
					case E_TRUE_4:
						if ((a == ' ') || (a == ';') || (a == ',')){
							lista.add(new TokenTabela(TokenTabela.IDTKN_BOOLEAN,
									TempToken));
							i--;
							achou = true;
						}
						else{
							i -= TempToken.length();
							estado = falhar(estado);
						}
						break;
					case E_FALSE_0:
						if (a == 'f'){
							TempToken += a;
							estado = E_FALSE_1;
						}
						else{
							i--;
							estado = falhar(estado);
						}
						break;
					case E_FALSE_1:
						if (a == 'a'){
							TempToken += a;
							estado = E_FALSE_2;
						}
						else{
							i -= TempToken.length();
							estado = falhar(estado);
						}
						break;
					case E_FALSE_2:
						if (a == 'l'){
							TempToken += a;
							estado = E_FALSE_3;
						}
						else{
							i -= TempToken.length();
							estado = falhar(estado);
						}
						break;
					case E_FALSE_3:
						if (a == 's'){
							TempToken += a;
							estado = E_FALSE_4;
						}
						else{
							i -= TempToken.length();
							estado = falhar(estado);
						}
						break;
					case E_FALSE_4:
						if (a == 'e'){
							TempToken += a;
							estado = E_FALSE_5;
						}
						else{
							i -= TempToken.length();
							estado = falhar(estado);
						}
						break;
					case E_FALSE_5:
						if ((a == ' ') || (a == ';') || (a == ',')){
							lista.add(new TokenTabela(TokenTabela.IDTKN_BOOLEAN,
									TempToken));
							i--;
							achou = true;
						}
						else{
							i -= TempToken.length();
							estado = falhar(estado);
						}
						break;
					case E_NOME_0:
						if ((Character.isLetter(a)) || (a == '_') ){
							TempToken += a;
							estado = E_NOME_1;
						}
						else{
							i--;
							estado = falhar(estado);
						}
						break;
					case E_NOME_1:
						if ((Character.isLetter(a)) || (Character.isDigit(a))
						   || (a == '_')){
							TempToken += a;
						}
						else if ((a == ' ') || (a == ';') || (a == '=') 
								|| (a == ',')||(a == '[')){
							lista.add(new TokenTabela(TokenTabela.IDTKN_NOME,
									TempToken));
							i--;
							achou = true;
						}
						else if (a == '.') {
							TempToken += a;
							estado = E_RECORD_0;
						}
						else{
							i -= TempToken.length();
							estado = falhar(estado);
						}
						break;
					case E_ARRAY_0:
						if (a == '['){
							estado = E_ARRAY_1;
						}
						else {
							i--;
							estado = falhar(estado);
						}
						break;
					case E_ARRAY_1:
						if (Character.isDigit(a)){
							TempToken += a;
						}
						else if (a == ']'){
							lista.add(new TokenTabela(TokenTabela.IDTKN_ARRAY,
									TempToken));
							achou = true;
						}
						break;
					case E_REAL_0:
						if(Character.isDigit(a)){
							TempToken += a;
							estado = E_REAL_1;
						}
						else if (a == '.'){
							TempToken += a;
							estado = E_REAL_2;
						}
						else{
							i--;
							estado = falhar(estado);
						}
						break;
					case E_REAL_1:
						if(Character.isDigit(a)){
							TempToken += a;
						}
						else if (a == '.'){
							TempToken += a;
							estado = E_REAL_2;
						}
						else if ((a == ' ') ||(a == ';') || (a == ',')){
							lista.add(new TokenTabela(TokenTabela.IDTKN_REAL,
									TempToken));
							i--;
							achou = true;
						}
						else{
							i -= TempToken.length();
							estado = falhar(estado);
						}
					case E_REAL_2:
						if(Character.isDigit(a)){
							TempToken += a;
						}
						else if ((a == ' ') || (a == ';') || (a == ',')){
							lista.add(new TokenTabela(TokenTabela.IDTKN_REAL,
									TempToken));
							i--;
							achou = true;
						}
						else{
							i -= TempToken.length();
							estado = falhar(estado);
						}
						break;
					case E_CHAR_0:
						if (a == '\''){
							estado = E_CHAR_1;
						}
						else{
							i--;
							estado = falhar(estado);
						}
						break;
					case E_CHAR_1:
						if (a == '\\'){
							TempToken += a;
							estado = E_CHAR_2;
						}
						else if (Character.isLetter(a)){
							TempToken += a;
							estado = E_CHAR_3;
						}
						else{
							i -= TempToken.length() + 1;
							estado = falhar(estado);
						}
						break;
					case E_CHAR_2:
						if ((a == '0') || (a == 'n') || (a == '\\') || 
						(a == '\'') || (a == '\"')){
							TempToken += a;
						}
						else{
							i -= TempToken.length() + 1;
							estado = falhar(estado);
						}
						break;
					case E_CHAR_3:
						if(a == '\''){
							lista.add(new TokenTabela(TokenTabela.IDTKN_CHAR,
									TempToken));
							achou = true;
						}
						else{
							i -= TempToken.length() + 2;
							estado = falhar(estado);
						}
						break;
					case E_STRING_0:
						if (a == '\"'){
							estado = E_STRING_1;
						}
						else{
							i--;
							estado = falhar(estado);
						}
						break;
					case E_STRING_1:
						if (a == '\"'){
							lista.add(new TokenTabela(TokenTabela.IDTKN_STRING,
									TempToken));
							achou = true;
						}
						else {
							TempToken += a;
						}
						break;
					case E_PONTO_E_VIRGULA_0:
						if (a == ';'){
							TempToken += a;
							lista.add(new TokenTabela(TokenTabela.IDTKN_PONTO_VIRGULA,
									TempToken));
							achou = true;
						}
						else{
							i--;
							estado = falhar(estado);
						}
						break;
					case E_VIRGULA_0:
						if (a == ','){
							TempToken += a;
							lista.add(new TokenTabela(TokenTabela.IDTKN_VIRGULA,
									TempToken));
							achou = true;
						}
						else{
							i--;
							estado = falhar(estado);
						}
						break;
					
					case E_RECORD_0:
						if ((Character.isLetter(a)) || (Character.isDigit(a))
								|| (a == '_')){
							TempToken += a;
						}
						else if ((a == ' ') || (a == ';') || (a == '=') 
								|| (a == ',')||(a == '[')){
							lista.add(new TokenTabela(TokenTabela.IDTKN_RECORD,
									TempToken));
							i--;
							achou = true;
						}
						else if (a == '.') {
							TempToken += a;
							estado = E_RECORD_0;
						}
						else{
							i -= TempToken.length();
							estado = falhar(estado);
						}
						break;
					case E_ATRIBUICAO_0:
						if (a == '='){
							TempToken += a;
							lista.add(new TokenTabela(TokenTabela.IDTKN_ATRIBUICAO,
									TempToken));
							achou = true;
						}
						else{
							i--;
							estado = falhar(estado);
						}
						break;
				}
				i++;
			}
			
			if (estado == 0){
				throw new Exception("Falha na análise léxica. \nNão foi possivél analisar a linha:\n" + linha);
			} else if (estado == E_COMENTARIO_0) {
				return;
			}
		}
		Sintatica sint = new Sintatica();
		sint.analisarSintaxe(lista, estado, records, mostrador, editor, console, ga);
		
	}
		
	private int falhar(int estado){
		switch(estado){
			case E_COMENTARIO_0:
				return E_ESPACO_BRANCO_0;
			case E_ESPACO_BRANCO_0:
				return E_INTEIRO_0;
			case E_INTEIRO_0:
			case E_INTEIRO_1:
				return E_TRUE_0;
			case E_TRUE_0:
			case E_TRUE_1:
			case E_TRUE_2:
			case E_TRUE_3:
			case E_TRUE_4:
				return E_FALSE_0;
			case E_FALSE_0:
			case E_FALSE_1:
			case E_FALSE_2:
			case E_FALSE_3:
			case E_FALSE_4:
			case E_FALSE_5:
				return E_NOME_0;
			case E_NOME_0:
			case E_NOME_1:
				return E_ARRAY_0;
			case E_ARRAY_0:
			case E_ARRAY_1:
				return E_REAL_0;
			case E_REAL_0:
			case E_REAL_1:
			case E_REAL_2:
				return E_CHAR_0;
			case E_CHAR_0:
			case E_CHAR_1:
			case E_CHAR_2:
			case E_CHAR_3:
				return E_STRING_0;
			case E_STRING_0:
			case E_STRING_1:
				return E_PONTO_E_VIRGULA_0;
			case E_PONTO_E_VIRGULA_0:
				return E_VIRGULA_0;
			case E_VIRGULA_0:
				return E_ATRIBUICAO_0;
			case E_ATRIBUICAO_0:
				return 0;
		}
		return 0;
	}
	
	
	private final int E_COMENTARIO_0	 	= 	1;
	private final int E_ESPACO_BRANCO_0		=	2;
	private final int E_INTEIRO_0			= 	3;
	private final int E_INTEIRO_1 			= 	4;
	private final int E_TRUE_0 				= 	5;
	private final int E_TRUE_1 				= 	6;
	private final int E_TRUE_2 				= 	7;
	private final int E_TRUE_3 				= 	8;
	private final int E_TRUE_4 				= 	9;
	private final int E_FALSE_0 			= 	10;
	private final int E_FALSE_1 			= 	11;
	private final int E_FALSE_2 			= 	12;
	private final int E_FALSE_3 			= 	13;
	private final int E_FALSE_4 			= 	14;
	private final int E_FALSE_5 			= 	15;
	private final int E_NOME_0 				= 	16;
	private final int E_NOME_1 				= 	17;
	private final int E_REAL_0 				= 	18;
	private final int E_REAL_1 				= 	19;
	private final int E_REAL_2				= 	20;
	private final int E_CHAR_0 				= 	21;
	private final int E_CHAR_1 				= 	22;
	private final int E_CHAR_2 				= 	23;
	private final int E_CHAR_3 				= 	24;
	private final int E_STRING_0 			= 	25;
	private final int E_STRING_1			= 	26;
	private final int E_PONTO_E_VIRGULA_0	= 	27;
	private final int E_VIRGULA_0			= 	28;
	private final int E_ATRIBUICAO_0		= 	29;
	private final int E_ARRAY_0				= 	30;
	private final int E_ARRAY_1				= 	31;
	private final int E_RECORD_0			= 	32;
}